package com.frame.easy.modular.activiti.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.frame.easy.common.constant.CommonConst;
import com.frame.easy.common.page.Page;
import com.frame.easy.exception.EasyException;
import com.frame.easy.modular.activiti.constant.ModelConst;
import com.frame.easy.modular.activiti.dao.ModelMapper;
import com.frame.easy.modular.activiti.model.Model;
import com.frame.easy.modular.activiti.service.ModelService;
import com.frame.easy.modular.sys.model.SysDownload;
import com.frame.easy.modular.sys.service.SysDownloadService;
import com.frame.easy.util.ToolUtil;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.persistence.entity.ModelEntity;
import org.activiti.engine.repository.Deployment;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.Arrays;
import java.util.List;

import static org.activiti.editor.constants.ModelDataJsonConstants.MODEL_DESCRIPTION;
import static org.activiti.editor.constants.ModelDataJsonConstants.MODEL_NAME;

/**
 * Activiti 流程模型
 * 部分代码来源于 Activiti（StencilsetRestResource.java、ModelEditorJsonRestResource.java、ModelSaveRestResource.java）
 *
 * @author tengchong
 * @date 2019-07-02
 */
@Service
public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements ModelService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private SysDownloadService sysDownloadService;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public Page select(Model model) {
        QueryWrapper<Model> queryWrapper = new QueryWrapper<>();
        if (model != null) {
            // 名称
            if (StrUtil.isNotBlank(model.getName())) {
                queryWrapper.like("arm.name_", model.getName());
            }
            // key
            if (StrUtil.isNotBlank(model.getKey())) {
                queryWrapper.like("arm.key_", model.getKey());
            }
        }
        Page page = ToolUtil.getPage(model);
        page.setRecords(getBaseMapper().select(page, queryWrapper));
        return page;
    }

    @Override
    public ModelEntity input(String id) {
        return (ModelEntity) repositoryService.getModel(id);
    }

    @Override
    public ObjectNode getModel(String id) {
        org.activiti.engine.repository.Model model = repositoryService.getModel(id);
        ObjectNode objectNode;
        if (model != null) {
            if (StrUtil.isNotBlank(model.getMetaInfo())) {
                try {
                    objectNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
                } catch (IOException e) {
                    logger.debug("读取模型信息失败[" + model.getId() + "]", e);
                    throw new EasyException("读取模型信息失败[" + model.getId() + "]");
                }
            } else {
                objectNode = objectMapper.createObjectNode();
                objectNode.put("name", model.getName());
            }
            objectNode.put("modelId", model.getId());
            ObjectNode editorJsonNode;
            try {
                editorJsonNode = (ObjectNode) objectMapper.readTree(new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));
            } catch (IOException e) {
                logger.debug("读取模型信息失败[" + model.getId() + "]", e);
                throw new EasyException("读取模型信息失败[" + model.getId() + "]");
            }
            objectNode.set("model", editorJsonNode);
        } else {
            throw new EasyException("模型信息不存在");
        }
        return objectNode;
    }

    @Override
    public ModelEntity add() {
        return new Model();
    }

    @Override
    public boolean delete(String ids) {
        if (StrUtil.isBlank(ids)) {
            throw new EasyException("获取数据ids失败");
        }
        List<String> idArray = Arrays.asList(ids.split(CommonConst.SPLIT));
        idArray.forEach(id -> repositoryService.deleteModel(id));
        return true;
    }

    @Override
    public ModelEntity saveData(Model model) {
        if (StrUtil.isBlank(model.getKey())) {
            throw new EasyException("标识不能为空");
        }
        if (StrUtil.isBlank(model.getName())) {
            throw new EasyException("名称不能为空");
        }
        if (StrUtil.isBlank(model.getId())) {
            model.setId(null);
        }
        if (StrUtil.isBlank(model.getCategory())) {
            model.setCategory(null);
        }
        //流程版本
        int revision = 1;

        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put(ModelDataJsonConstants.MODEL_NAME, model.getName());
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, model.getDescription());
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);
        model.setMetaInfo(modelNode.toString());
        // 保存模型信息
        ModelEntity modelEntity = model.getModelEntity();
        repositoryService.saveModel(modelEntity);
        // ModelEditorSource
        String modelId = modelEntity.getId();
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put(ModelConst.ID, "canvas");
        editorNode.put("resourceId", "canvas");

        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.set("stencilset", stencilSetNode);

        try {
            repositoryService.addModelEditorSource(modelId, editorNode.toString().getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            logger.debug("创建流程模型失败", e);
            throw new EasyException(e.getMessage());
        }
        return modelEntity;
    }

    @Override
    public void saveModel(String id, String name, String description, String jsonXml, String svgXml) {
        org.activiti.engine.repository.Model model = repositoryService.getModel(id);
        if (model != null) {
            ObjectNode modelJson = null;
            try {
                modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
            } catch (IOException e) {
                e.printStackTrace();
            }
            modelJson.put(MODEL_NAME, name);
            modelJson.put(MODEL_DESCRIPTION, description);
            model.setMetaInfo(modelJson.toString());
            model.setName(name);

            // 保存
            repositoryService.saveModel(model);
            try {
                repositoryService.addModelEditorSource(model.getId(), jsonXml.getBytes("utf-8"));
                InputStream svgStream = new ByteArrayInputStream(svgXml.getBytes("utf-8"));
                TranscoderInput input = new TranscoderInput(svgStream);

                PNGTranscoder transcoder = new PNGTranscoder();
                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                TranscoderOutput output = new TranscoderOutput(outStream);

                transcoder.transcode(input, output);
                final byte[] result = outStream.toByteArray();
                repositoryService.addModelEditorSourceExtra(model.getId(), result);
                outStream.close();

            } catch (IOException | TranscoderException e) {
                logger.info("保存模型信息失败", e);
                throw new EasyException(e.getMessage());
            }


        } else {
            throw new EasyException("获取模型信息失败[modelId = " + id + "]");
        }
    }

    @Override
    public String getStencilset() {
        InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset.json");
        try {
            return IOUtils.toString(stencilsetStream, "utf-8");
        } catch (Exception e) {
            throw new ActivitiException("读取失败", e);
        }
    }

    @Override
    public boolean deploymentProcess(String id) {
        if (StrUtil.isBlank(id)) {
            throw new EasyException("获取模型id信息失败");
        }
        // 获取模型信息
        org.activiti.engine.repository.Model model = repositoryService.getModel(id);
        if (model == null) {
            throw new EasyException("模型信息不存在");
        }
        byte[] bytes = repositoryService.getModelEditorSource(id);
        if (bytes == null) {
            throw new EasyException("模型信息为空");
        }
        // 读取模型信息
        JsonNode jsonNode;
        try {
            jsonNode = new ObjectMapper().readTree(bytes);
        } catch (IOException e) {
            logger.debug("读取模型信息失败", e);
            throw new EasyException("读取模型信息失败" + e.getMessage());
        }
        if (jsonNode == null) {
            throw new EasyException("读取模型信息失败");
        }
        // 验证模型结构
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode);
        if (bpmnModel.getProcesses().size() == 0) {
            throw new EasyException("请至少设计一条主线流程");
        }
        byte[] modelBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
        // 部署流程
        String processeName = model.getKey() + ".bpmn20.xml";
        try {
            Deployment deployment = repositoryService.createDeployment().name(model.getName())
                    .addString(processeName, new String(modelBytes, "UTF-8"))
                    .deploy();
            // 设置模型关联的流程id
            model.setDeploymentId(deployment.getId());
            repositoryService.saveModel(model);
        } catch (UnsupportedEncodingException e) {
            logger.debug("部署流程失败[" + model.getId() + "]", e);
            throw new EasyException("部署流程失败[" + model.getId() + "]" + e.getMessage());
        }
        return true;
    }

    @Override
    public String selectProcessDefinitionId(String key) {
        String processDefinitionId = getBaseMapper().selectProcessDefinitionId(key);
        if(StrUtil.isBlank(processDefinitionId)){
            throw new EasyException("流程不存在或未部署，请联系系统管理员");
        }
        return processDefinitionId;
    }

    @Override
    public String export(String id) {
        org.activiti.engine.repository.Model model = repositoryService.getModel(id);
        BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
        JsonNode editorNode = null;
        try {
            editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(model.getId()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
        BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
        byte[] bpmnModelXmlByte = xmlConverter.convertToXML(bpmnModel);
        // 写入文件
        String bpmnPath = com.frame.easy.util.file.FileUtil.getTemporaryPath() + IdUtil.randomUUID() + ".bpmn20.xml";
        FileUtil.writeBytes(bpmnModelXmlByte, new File(bpmnPath));

        SysDownload sysDownload = new SysDownload();
        sysDownload.setName(model.getName() + " v." + + model.getVersion() + ".bpmn20.xml");
        sysDownload.setPath(bpmnPath);
        return sysDownloadService.saveData(sysDownload).getId();
    }
}
